package collections.map.hash_map;

import java.util.Iterator;

import collections.list.linked_list.MyLinkedList;
import collections.map.MyMap;

public class MyHashMap<K, V> implements MyMap<K, V> {

	public static final int DEFAULT_CAPACITY = 16;
	public static final float DEFAULT_LOAD_FACTOR = 0.75F;

	private int capacity;
	private float loadFactor;
	private int size = 0;
	private MyLinkedList<Entry<K, V>>[] values;

	public MyHashMap() {
		capacity = DEFAULT_CAPACITY;
		loadFactor = DEFAULT_LOAD_FACTOR;
		values = (MyLinkedList<Entry<K, V>>[]) new MyLinkedList[capacity];
	}

	public MyHashMap(int initialCapacity) {
		capacity = initialCapacity;
		loadFactor = DEFAULT_LOAD_FACTOR;
		values = (MyLinkedList<Entry<K, V>>[]) new MyLinkedList[capacity];
	}

	public MyHashMap(int initialCapacity, float loadFactor) {
		this.capacity = initialCapacity;
		this.loadFactor = loadFactor;
		values = (MyLinkedList<Entry<K, V>>[]) new MyLinkedList[capacity];
	}

	public class SimpleEntry<K, V> implements Entry<K, V> {

		K key;
		V value;

		public SimpleEntry(K key, V value) {
			this.key = key;
			this.value = value;
		}

		@Override
		public K getKey() {
			return key;
		}

		@Override
		public V getValue() {
			return value;
		}

		@Override
		public V setValue(V value) {
			V result = this.value;
			this.value = value;
			return result;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((key == null) ? 0 : key.hashCode());
			result = prime * result + ((value == null) ? 0 : value.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			SimpleEntry other = (SimpleEntry) obj;
			if (key == null) {
				if (other.key != null)
					return false;
			} else if (!key.equals(other.key))
				return false;
			if (value == null) {
				if (other.value != null)
					return false;
			} else if (!value.equals(other.value))
				return false;
			return true;
		}
	}

	@Override
	public void clear() {
		size = 0;
		values = (MyLinkedList<Entry<K, V>>[]) new MyLinkedList[capacity];
	}

	@Override
	public boolean containsKey(K key) {
		return get(key) != null;
	}

	@Override
	public boolean containsValue(V value) {
		for (MyLinkedList<Entry<K, V>> entrys : values) {
			if (entrys != null) {
				for (Entry<K, V> entry : entrys) {
					if (entry.getValue().equals(value)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	@Override
	public V get(K key) {
		MyLinkedList<Entry<K, V>> list = values[getKey(key.hashCode(), capacity)];
		if (list != null) {
			for (Entry<K, V> entry : list) {
				if (entry.getKey().equals(key)) {
					return entry.getValue();
				}
			}
		}
		return null;
	}

	private int getKey(int hash, int length) {
		return hash & (length - 1);
	}

	@Override
	public boolean isEmpty() {
		if (size == 0) {
			return true;
		}
		return false;
	}

	@Override
	public V put(K key, V value) {
		V result = null;
		if (get(key) == null) {
			if (capacity * loadFactor < size) {
				capacity *= 2;
				MyLinkedList<Entry<K, V>>[] tempValues = values;
				size = 0;
				values = (MyLinkedList<Entry<K, V>>[]) new MyLinkedList[capacity];
				for (MyLinkedList<Entry<K, V>> list : tempValues) {
					if (list != null) {
						for (Entry<K, V> entry : list) {
							put(entry.getKey(), entry.getValue());
						}
					}
				}
			} else {
				if (values[getKey(key.hashCode(), capacity)] == null) {
					values[getKey(key.hashCode(), capacity)] = new MyLinkedList<>();
					values[getKey(key.hashCode(), capacity)]
							.add(new SimpleEntry(key, value));
				} else {
					values[getKey(key.hashCode(), capacity)]
							.add(new SimpleEntry(key, value));
				}
			}
		} else {
			result = remove(key);
			if (values[getKey(key.hashCode(), capacity)] == null) {
				values[getKey(key.hashCode(), capacity)] = new MyLinkedList<>();
				values[getKey(key.hashCode(), capacity)].add(new SimpleEntry(
						key, value));
			} else {
				values[getKey(key.hashCode(), capacity)].add(new SimpleEntry(
						key, value));
			}
		}
		size++;
		return result;
	}

	@Override
	public V remove(K key) {
		if (!containsKey(key)) {
			return null;
		}
		V result = null;
		int bucketNumber = getKey(key.hashCode(), capacity);
		MyLinkedList<Entry<K, V>> list = values[bucketNumber];
		int entryToDelete = 0;
		int count = 0;
		for (Entry<K, V> entry : list) {
			if (entry.getKey().equals(key)) {
				result = entry.getValue();
				entryToDelete = count;
			}
			count++;
		}
		if (list.size() == 1) {
			values[bucketNumber] = null;
		} else {
			list.remove(entryToDelete);
		}
		size--;
		return result;
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public Iterator<Entry<K, V>> entryIterator() {

		return new Iterator<Entry<K, V>>() {

			private int currentBucket = 0;
			private int currentNumber = 0;
			private MyLinkedList<Entry<K, V>> list;
			private Iterator<Entry<K, V>> listIterator;
			private Entry<K, V> result;

			@Override
			public boolean hasNext() {
				return currentNumber < size;
			}

			@Override
			public Entry<K, V> next() {
				if (list == null) {
					while (currentBucket < capacity - 1
							&& values[currentBucket] == null) {
						currentBucket++;
					}
					if (values[currentBucket] != null) {
						list = values[currentBucket];
						listIterator = list.iterator();
						result = listIterator.next();
						currentNumber++;
					}
				} else {
					if (listIterator.hasNext()) {
						result = listIterator.next();
						currentNumber++;
					} else {
						list = null;
						listIterator = null;
						currentBucket++;
						while (currentBucket < capacity - 1
								&& values[currentBucket] == null) {
							currentBucket++;
						}
						if (values[currentBucket] != null) {
							list = values[currentBucket];
							listIterator = list.iterator();
							result = listIterator.next();
							currentNumber++;
						}

					}
				}
				return result;
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}
}
